﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
using SharePointUtilitiesForDevelopers.Action;
using SharePointUtilitiesForDevelopers.Repository;

namespace SharePointUtilitiesForDevelopers.Client.Forms
{
    /// <summary>
    /// Main application form.
    /// </summary>
    public partial class MainForm : Form
    {
        #region Constants

        private const string DocumentLibraryImageTag = "DocumentLibrary";
        private const string WebImageTag = "Web";
        private const string ListImageTag = "List";
        private const string ColumnImageTag = "Column";
        private const string ContentTypeImageKey = "ContentType";

        #endregion Constants

        #region Fields

        private SharePointWeb _web;
        private OpenSiteForm _openSiteForm;
        private readonly ISharePointWebRepository _webRepository;
        private readonly ISharePointListRepository _listRepository;
        private readonly IActionManager _actionManager;

        #endregion Fields

        #region Constructors
        
        /// <summary>
        /// Initializes a new instance of the <see cref="MainForm"/> class.
        /// </summary>
        /// <param name="webRepository">The web repository.</param>
        /// <param name="listRepository">The list repository.</param>
        /// <param name="actionManager">The action manager.</param>
        public MainForm(
            ISharePointWebRepository webRepository, 
            ISharePointListRepository listRepository, 
            IActionManager actionManager)
        {
            InitializeComponent();

            // Get dependencies
            _webRepository = webRepository;
            _listRepository = listRepository;
            _actionManager = actionManager;
            
        }

        #endregion Constructors

        #region Event Handlers

        /// <summary>
        /// Handles the Load event of the MainForm control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void MainForm_Load(object sender, EventArgs e)
        {
            try
            {
                // Show the open site dialog
                if (ShowOpenSite())
                {
                    // Show wait
                    getSiteButton.Enabled = false;
                    Cursor = Cursors.WaitCursor;

                    // Bind the tree view
                    InitializeTreeView();

                    // Load actions
                    LoadActions();

                }

            }
            catch (Exception ex)
            {
                ErrorReport.HandleException(this, ex);
            }
            finally
            {
                getSiteButton.Enabled = true;
                Cursor = Cursors.Default;
            }
        }

        /// <summary>
        /// Handles the MouseDown event of the siteTreeView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        private void siteTreeView_MouseDown(object sender, MouseEventArgs e)
        {
            try
            {
                // Set the selected node
                siteTreeView.SelectedNode = siteTreeView.GetNodeAt(e.X, e.Y);
            }
            catch (Exception ex)
            {
                ErrorReport.HandleException(this, ex);
            }
        }

        /// <summary>
        /// Handles the AfterSelect event of the siteTreeView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.TreeViewEventArgs"/> instance containing the event data.</param>
        private void siteTreeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            try
            {
                // Get node info
                var nodeInfo = GetTreeNodeInfo(e.Node);
                if (nodeInfo != null)
                {
                    // Show the element in the property grid
                    propertyGrid.SelectedObject = nodeInfo.Tag;
                }
            }
            catch (Exception ex)
            {
                ErrorReport.HandleException(this, ex);
            }
        }

        /// <summary>
        /// Handles the Click event of the getSiteButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void getSiteButton_Click(object sender, EventArgs e)
        {
            try
            {
                // Show the open site dialog
                if (ShowOpenSite())
                {
                    // Show wait
                    getSiteButton.Enabled = false;
                    Cursor = Cursors.WaitCursor;

                    // Bind the tree view
                    InitializeTreeView();
                }

            }
            catch (Exception ex)
            {
                ErrorReport.HandleException(this, ex);
            }
            finally
            {
                getSiteButton.Enabled = true;
                Cursor = Cursors.Default;
            }
        }

        /// <summary>
        /// Handles the BeforeExpand event of the siteTreeView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.TreeViewCancelEventArgs"/> instance containing the event data.</param>
        private void siteTreeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            try
            {
                // Show wait
                Cursor = Cursors.WaitCursor;

                // Load node on demand
                EnsureNodeLoaded(e.Node);

            }
            catch (Exception ex)
            {
                ErrorReport.HandleException(this, ex);
            }
            finally
            {
                Cursor = Cursors.Default;
            }
        }

        /// <summary>
        /// Handles the Click event of an action menu item.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void Action_MenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                // Show wait
                Cursor = Cursors.WaitCursor;

                // Get the menu item
                var menuItem = sender as ToolStripMenuItem;
                if (menuItem == null)
                {
                    return;
                }

                // Get the node info
                var nodeInfo = GetTreeNodeInfo(siteTreeView.SelectedNode);
                if (nodeInfo == null)
                {
                    return;
                }

                // Get the action
                var action = menuItem.Tag as IAction;
                if (action == null)
                {
                    return;
                }

                // Create the context
                var context = new ActionContext {Entity = nodeInfo.Tag, Owner = this, Web = _web};

                // Execute
                action.Execute(context);
            }
            catch (Exception ex)
            {
                ErrorReport.HandleException(this, ex);
            }
            finally
            {
                Cursor = Cursors.Default;
            }
        }

        /// <summary>
        /// Handles the Opening event of the treeContextMenu control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        private void treeContextMenu_Opening(object sender, CancelEventArgs e)
        {
            try
            {
                IEnumerable<IAction> actions = null;
                
                // Get the node info
                var nodeInfo = GetTreeNodeInfo(siteTreeView.SelectedNode);
                if (nodeInfo != null)
                {
                    // Enable context-specific menu items
                    switch (nodeInfo.NodeType)
                    {
                        case TreeNodeType.List:
                            actions = _actionManager.GetActions(ActionTarget.List);
                            break;
                        case TreeNodeType.ListView:
                            actions = _actionManager.GetActions(ActionTarget.ListView);
                            break;
                    }
                }

                // Check each menu item
                foreach (ToolStripItem item in treeContextMenu.Items)
                {
                    // Determine if the menu item is an action
                    var action = item.Tag as IAction;
                    if (action == null)
                    {
                        continue;
                    }

                    item.Enabled = (action.Target == ActionTarget.All) || 
                        ((actions != null) && actions.Contains(action));
                }

            }
            catch (Exception ex)
            {
                ErrorReport.HandleException(this, ex);
            }
        }

        #endregion Event Handlers

        #region Helper Methods

        /// <summary>
        /// Finds the first parent of the specified type.
        /// </summary>
        /// <param name="nodeType">Type of the node.</param>
        /// <param name="node">The node.</param>
        /// <returns>The first tree node of the specified type.</returns>
        private TreeNodeInfo FindFirstParent(TreeNodeType nodeType, TreeNode node)
        {
            // Nothing to check
            if (node == null)
            {
                return null;
            }

            // Is the current node of the type
            var nodeInfo = GetTreeNodeInfo(node);
            if (nodeInfo.NodeType == nodeType)
            {
                return nodeInfo;
            }

            // Check the parent
            return FindFirstParent(nodeType, node.Parent);
        }

        /// <summary>
        /// Ensures the node loaded.
        /// </summary>
        /// <param name="node">The node.</param>
        private void EnsureNodeLoaded(TreeNode node)
        {
            // If no tag or not tree node info, return
            if ((node.Tag == null) || (!(node.Tag is TreeNodeInfo)))
            {
                return;
            }

            // If the children are loaded, return
            var nodeInfo = node.Tag as TreeNodeInfo;
            if (nodeInfo.ChildrenLoaded)
            {
                return;
            }

            // Clear existing dummy nodes
            node.Nodes.Clear();

            switch (nodeInfo.NodeType)
            {
                case TreeNodeType.SiteContentTypes:
                    LoadSiteContentTypes(node);
                    break;

                case TreeNodeType.SiteLists:
                    LoadSiteLists(node);
                    break;

                case TreeNodeType.List:
                    LoadList(node);
                    break;

                case TreeNodeType.ListViews:
                    LoadListViews(node);
                    break;

                case TreeNodeType.ListContentTypes:
                    LoadListContentTypes(node);
                    break;

                case TreeNodeType.ListContentType:
                    LoadListContentType(node);
                    break;

                case TreeNodeType.ListContentTypeFields:
                    LoadListContentTypeFields(node);
                    break;

                case TreeNodeType.ListFields:
                    LoadListFields(node);
                    break;

                case TreeNodeType.ListAllFields:
                    LoadListAllFields(node);
                    break;

                case TreeNodeType.ListCustomFields:
                    LoadListCustomFields(node);
                    break;
            }

            // Flag as loaded
            nodeInfo.ChildrenLoaded = true;
        }

        /// <summary>
        /// Loads the actions.
        /// </summary>
        private void LoadActions()
        {
            // Load the actions
            _actionManager.LoadActions();
            
            // Get all loaded actions
            var actions = _actionManager.GetActions(ActionTarget.All);

            // Add menu items for each action
            foreach (var action in actions)
            {
                var menuItem = new ToolStripMenuItem(action.Title) {Tag = action};
                menuItem.Click += Action_MenuItem_Click;
                treeContextMenu.Items.Add(menuItem);
            }
        }

        /// <summary>
        /// Initializes the tree view.
        /// </summary>
        private void InitializeTreeView()
        {
            // Clear the tree
            siteTreeView.Nodes.Clear();

            // Create the root node
            var rootNode = new TreeNode(_web.Title);
            rootNode.Tag = new TreeNodeInfo(rootNode.Text, TreeNodeType.Site, _web) { ChildrenLoaded = true };
            rootNode.SelectedImageIndex = imageList.Images.IndexOfKey(WebImageTag);
            rootNode.ImageIndex = imageList.Images.IndexOfKey(WebImageTag);
            siteTreeView.Nodes.Add(rootNode);

            // Content types
            var contentTypesNode = new TreeNode(Properties.Resources.NodeSiteContentTypes);
            contentTypesNode.Tag = new TreeNodeInfo(contentTypesNode.Text, Properties.Resources.NodeSiteContentTypesDescription, TreeNodeType.SiteContentTypes, null);
            contentTypesNode.Nodes.Add(new TreeNode());
            rootNode.Nodes.Add(contentTypesNode);

            // Lists
            var listsNode = new TreeNode(Properties.Resources.NodeSiteLists);
            listsNode.Tag = new TreeNodeInfo(listsNode.Text, Properties.Resources.NodeSiteListsDescription, TreeNodeType.SiteLists, null);
            listsNode.Nodes.Add(new TreeNode());
            rootNode.Nodes.Add(listsNode);

            rootNode.Expand();
        }

        /// <summary>
        /// Loads the list.
        /// </summary>
        /// <param name="node">The node.</param>
        private void LoadList(TreeNode node)
        {
            // Clear
            node.Nodes.Clear();

            // Create the views node
            var viewsNode = new TreeNode(Properties.Resources.NodeListViews);
            viewsNode.Tag = new TreeNodeInfo(viewsNode.Text, Properties.Resources.NodeListViewsDescription, TreeNodeType.ListViews, null);
            viewsNode.Nodes.Add(new TreeNode());
            node.Nodes.Add(viewsNode);

            // Create the fields node
            var fieldsNode = new TreeNode(Properties.Resources.NodeListFields);
            fieldsNode.Tag = new TreeNodeInfo(fieldsNode.Text, Properties.Resources.NodeListFieldsDescription, TreeNodeType.ListFields, null);
            fieldsNode.Nodes.Add(new TreeNode());
            node.Nodes.Add(fieldsNode);

            // Create the content types node
            var contentTypesNode = new TreeNode(Properties.Resources.NodeListContentTypes);
            contentTypesNode.Tag = new TreeNodeInfo(contentTypesNode.Text, Properties.Resources.NodeListContentTypesDescription, TreeNodeType.ListContentTypes, null);
            contentTypesNode.Nodes.Add(new TreeNode());
            node.Nodes.Add(contentTypesNode);
        }

        /// <summary>
        /// Loads the list fields.
        /// </summary>
        /// <param name="node">The node.</param>
        private void LoadListFields(TreeNode node)
        {
            // Clear
            node.Nodes.Clear();

            // Create All Fields node
            var allFieldsNode = new TreeNode(Properties.Resources.NodeListAllFields);
            allFieldsNode.Tag = new TreeNodeInfo(allFieldsNode.Text, Properties.Resources.NodeListAllFieldsDescription, TreeNodeType.ListAllFields, null);
            allFieldsNode.Nodes.Add(new TreeNode());
            node.Nodes.Add(allFieldsNode);

            // Create Custom Fields node
            var custFieldsNode = new TreeNode(Properties.Resources.NodeListCustomFields);
            custFieldsNode.Tag = new TreeNodeInfo(custFieldsNode.Text, Properties.Resources.NodeListCustomFieldsDescription, TreeNodeType.ListCustomFields, null);
            custFieldsNode.Nodes.Add(new TreeNode());
            node.Nodes.Add(custFieldsNode);
        }

        /// <summary>
        /// Loads the list all fields.
        /// </summary>
        /// <param name="node">The node.</param>
        private void LoadListAllFields(TreeNode node)
        {
            // Clear
            node.Nodes.Clear();

            // Get the list
            var nodeInfo = FindFirstParent(TreeNodeType.List, node);
            if (nodeInfo != null)
            {
                var list = (SharePointList) nodeInfo.Tag;

                // Get the fields
                if (list.Fields == null)
                {
                    _listRepository.LoadListFields(list, _web.Url);
                }

                if (list.Fields != null)
                {
                    // Add each field
                    foreach (var fld in list.Fields)
                    {
                        var fldNode = new TreeNode(fld.FormattedName);
                        fldNode.Tag = new TreeNodeInfo(fldNode.Text, TreeNodeType.ListField, fld);
                        fldNode.SelectedImageKey = ColumnImageTag;
                        fldNode.ImageKey = ColumnImageTag;
                        node.Nodes.Add(fldNode);
                    }
                }
            }
        }

        /// <summary>
        /// Loads the list custom fields.
        /// </summary>
        /// <param name="node">The node.</param>
        private void LoadListCustomFields(TreeNode node)
        {
            // Clear
            node.Nodes.Clear();

            // Get the list
            var nodeInfo = FindFirstParent(TreeNodeType.List, node);
            if (nodeInfo != null)
            {
                var list = (SharePointList) nodeInfo.Tag;

                // Get the fields
                if (list.CustomFields == null)
                {
                    _listRepository.LoadListFields(list, _web.Url);
                }

                if (list.CustomFields != null)
                {
                    // Add each field
                    foreach (var fld in list.CustomFields)
                    {
                        var fldNode = new TreeNode(fld.FormattedName);
                        fldNode.Tag = new TreeNodeInfo(fldNode.Text, TreeNodeType.ListField, fld);
                        fldNode.SelectedImageKey = ColumnImageTag;
                        fldNode.ImageKey = ColumnImageTag;
                        node.Nodes.Add(fldNode);
                    }
                }
            }
        }

        /// <summary>
        /// Loads the list views.
        /// </summary>
        /// <param name="node">The node.</param>
        private void LoadListViews(TreeNode node)
        {
            // Clear
            node.Nodes.Clear();

            // Get the list
            var nodeInfo = FindFirstParent(TreeNodeType.List, node);
            if (nodeInfo != null)
            {
                var list = (SharePointList) nodeInfo.Tag;

                // Get the views
                if (list.Views == null)
                {
                    _listRepository.LoadListViews(list, _web.Url);
                }

                if (list.Views != null)
                {
                    // Ad each view
                    foreach (var view in list.Views)
                    {
                        var viewNode = new TreeNode(view.FormattedName);
                        viewNode.Tag = new TreeNodeInfo(viewNode.Text, TreeNodeType.ListView, view);
                        viewNode.SelectedImageKey = ColumnImageTag;
                        viewNode.ImageKey = ColumnImageTag;
                        node.Nodes.Add(viewNode);
                    }
                }
            }
        }

        /// <summary>
        /// Loads the list content types.
        /// </summary>
        /// <param name="node">The node.</param>
        private void LoadListContentTypes(TreeNode node)
        {
            // Clear
            node.Nodes.Clear();

            // Get the list
            var nodeInfo = FindFirstParent(TreeNodeType.List, node);
            if (nodeInfo != null)
            {
                var list = (SharePointList) nodeInfo.Tag;

                // Get the content types
                if (list.ContentTypes == null)
                {
                    _listRepository.LoadListContentTypes(list, _web.Url);
                }

                if (list.ContentTypes != null)
                {
                    // Add each content type
                    foreach (var ct in list.ContentTypes)
                    {
                        // Create content type node
                        var ctNode = new TreeNode(ct.Name);
                        ctNode.Tag = new TreeNodeInfo(ctNode.Text, TreeNodeType.ListContentType, ct);
                        ctNode.SelectedImageKey = ContentTypeImageKey;
                        ctNode.ImageKey = ContentTypeImageKey;
                        ctNode.Nodes.Add(new TreeNode());
                        node.Nodes.Add(ctNode);
                    }
                }
            }
        }

        /// <summary>
        /// Loads the list content type.
        /// </summary>
        /// <param name="node">The node.</param>
        private void LoadListContentType(TreeNode node)
        {
            // Clear the nodes
            node.Nodes.Clear();

            // Create the content type fields node
            var ctFieldsNode = new TreeNode(Properties.Resources.NodeListContentTypeFields);
            ctFieldsNode.Tag = new TreeNodeInfo(ctFieldsNode.Text, Properties.Resources.NodeListContentTypeFieldsDescription, TreeNodeType.ListContentTypeFields, null);
            ctFieldsNode.Nodes.Add(new TreeNode());
            node.Nodes.Add(ctFieldsNode);

        }

        /// <summary>
        /// Loads the list content type fields.
        /// </summary>
        /// <param name="node">The node.</param>
        private void LoadListContentTypeFields(TreeNode node)
        {
            // Clear
            node.Nodes.Clear();

            // Get the list
            var nodeInfo = FindFirstParent(TreeNodeType.ListContentType, node);
            if (nodeInfo != null)
            {
                var contentType = (SharePointContentType) nodeInfo.Tag;

                if (contentType.Fields != null)
                {
                    // Add each content type field
                    foreach (var ctFld in contentType.Fields/*.Where(f => !f.Hidden)*/)
                    {
                        var ctFldNode =
                            new TreeNode(ctFld.FormattedName);
                        ctFldNode.Tag = new TreeNodeInfo(ctFldNode.Text, TreeNodeType.ListContentTypeField, ctFld);
                        ctFldNode.SelectedImageKey = ColumnImageTag;
                        ctFldNode.ImageKey = ColumnImageTag;
                        node.Nodes.Add(ctFldNode);
                    }
                }
            }
        }

        /// <summary>
        /// Loads the site lists.
        /// </summary>
        /// <param name="node">The node.</param>
        private void LoadSiteLists(TreeNode node)
        {
            // Clear the nodes
            node.Nodes.Clear();

            // Get the web
            var nodeInfo = FindFirstParent(TreeNodeType.Site, node);
            if (nodeInfo != null)
            {
                var web = (SharePointWeb) nodeInfo.Tag;

                // Get the lists
                if (web.Lists == null)
                {
                    _webRepository.LoadWebLists(web);
                }

                if (web.Lists != null)
                {
                    // Add each list
                    foreach (var list in web.Lists/*.Where(l => !l.Hidden)*/)
                    {
                        // Create the list node
                        var listNode = new TreeNode(list.Title);
                        listNode.Tag = new TreeNodeInfo(listNode.Text, TreeNodeType.List, list);
                        listNode.ToolTipText = list.Description;
                        string imageTag =
                            (list.BaseType == SharePointListBaseType.DocumentLibrary)
                                ?
                                    DocumentLibraryImageTag
                                :
                                    ListImageTag;
                        listNode.ImageKey = imageTag;
                        listNode.SelectedImageKey = imageTag;
                        listNode.Nodes.Add(new TreeNode());

                        // Add to root
                        node.Nodes.Add(listNode);
                    }
                }
            }
        }

        /// <summary>
        /// Loads the site content types.
        /// </summary>
        /// <param name="node">The node.</param>
        private void LoadSiteContentTypes(TreeNode node)
        {
            // Clear the nodes
            node.Nodes.Clear();

            // Get the web
            var nodeInfo = FindFirstParent(TreeNodeType.Site, node);
            if (nodeInfo != null)
            {
                var web = (SharePointWeb) nodeInfo.Tag;

                // Get the content types
                if (web.ContentTypes == null)
                {
                    _webRepository.LoadWebContentTypes(web);
                }

                if (web.ContentTypes != null)
                {
                    // Add each content type
                    foreach (var contentType in web.ContentTypes/*.Where(c => !c.Hidden)*/)
                    {
                        var contentTypeNode = new TreeNode(contentType.Name);
                        contentTypeNode.Tag = new TreeNodeInfo(contentTypeNode.Text, TreeNodeType.SiteContentType,
                                                               contentType);
                        contentTypeNode.ImageKey = ContentTypeImageKey;
                        contentTypeNode.SelectedImageKey = ContentTypeImageKey;
                        contentTypeNode.Nodes.Add(new TreeNode());
                        node.Nodes.Add(contentTypeNode);
                    }
                }
            }
        }

        /// <summary>
        /// Shows the open site form.
        /// </summary>
        private bool ShowOpenSite()
        {
            // Create the form
            if (_openSiteForm == null || _openSiteForm.IsDisposed)
            {
                _openSiteForm = ServiceLocator.Resolve<OpenSiteForm>();
            }

            // Configure the form
            _openSiteForm.SiteUrl = siteUrlTextBox.Text;
            _openSiteForm.WebLoadOptions = new SharePointWebLoadOptions();

            // Show the form
            var result = _openSiteForm.ShowDialog(this);
            if (result == DialogResult.OK)
            {
                siteUrlTextBox.Text = _openSiteForm.SiteUrl;
                _web = _openSiteForm.Web;
            }

            return result == DialogResult.OK;
        }

        /// <summary>
        /// Gets the tree node info.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <returns>Null if the node is null or the tag isn't a TreeNodeInfo.</returns>
        private TreeNodeInfo GetTreeNodeInfo(TreeNode node)
        {
            if (node == null)
            {
                return null;
            }
            if (node.Tag == null)
            {
                return null;
            }
            if (!(node.Tag is TreeNodeInfo))
            {
                return null;
            }
            return (TreeNodeInfo)node.Tag;
        }

        #endregion Helper Methods
    }
}